/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.debugger.support.util;
import java.lang.ref.*;
import java.util.*;
import org.openide.TopManager;
/** Request processor that is capable to execute actions in
* special thread.
*
* @author Jaroslav Tulach
*/
final class RequestProcessor extends Object {
// JST: final can be removed if needed
/** the static instance for users that do not want to have own processor */
private static RequestProcessor DEFAULT = new RequestProcessor ();
/** number of processors */
private static int processorCount = 0;
private static long counter = 0;
/** sorted set of all task that are waiting to be processed
* objects of type (Holder), sorted by time
*/
final SortedSet waiting = new TreeSet (new TimeComp ());
/** thread to process requests */
private ProcessorThread thread;
/** name of the request processor or null */
private String name;
/** Default constructor.
*/
public RequestProcessor () {
}
/** Constructor.
* @param name the name to use for the request processor thread
*/
public RequestProcessor (String name) {
this.name = name;
}
/** When finalized, stops the thread.
*/
protected void finalize () {
stop ();
}
/** This methods asks the request processor to start given
* runnable after timeToWait milliseconds.
*
* @param run class to run
* @return the task to control the request
*/
public Task post (Runnable run) {
return post (run, 0, Thread.MIN_PRIORITY);
}
/** This methods asks the request processor to start given
* runnable after timeToWait milliseconds. The default priority is Thread.MIN_PRIORITY.
*
* @param run class to run
* @param timeToWait to wait before execution
* @return the task to control the request
*/
public Task post (final Runnable run, int timeToWait) {
return post (run, timeToWait, Thread.MIN_PRIORITY);
}
/** This methods asks the request processor to start given
* runnable after timeToWait milliseconds. Given priority is assigned to the
* request.
*
*
* @param run class to run
* @param timeToWait to wait before execution
* @param priority the priority from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY
* @return the task to control the request
*/
public Task post (final Runnable run, int timeToWait, int priority) {
Task t;
synchronized (waiting) {
t = new Task (run, timeToWait, priority);
waiting.add (t.createHolder ());
checkTimerQueue ();
}
// debug code
if (debug != null && RequestProcessor.this == DEFAULT) {
debug.notifyPost (t);
}
// end of debug
return t;
}
/** Creates request that can be later started by setting its delay.
* The request is not immediatelly put into the queue. It is planned after
* setting its delay by setDelay method.
*
* @param run action to run in the process
* @return the task to control execution of given action
*/
public Task create (Runnable run) {
return new Task (run, 0, Thread.MIN_PRIORITY);
}
/** Tests if the current thread is request processor thread.
* This method could be used to prevent the deadlocks using
* <CODE>waitFinished</CODE> method. Any two tasks created
* by request processor must not wait for themself.
*
* @return <CODE>true</CODE> if the current thread is request processor
* thread, otherwise <CODE>false</CODE>
*/
public boolean isRequestProcessorThread () {
return Thread.currentThread().equals(thread);
}
/** Stops processing of runnables processor.
* The currently running runnable is finished and no new is started.
*/
public void stop () {
synchronized (waiting) {
if (thread != null) {
thread.stopProcessing ();
thread = null;
}
}
}
//
// Static methods communicating with default request processor
//
/** This methods asks the request processor to start given
* runnable after timeToWait milliseconds.
*
* @param run class to run
* @return the task to control the request
*/
public static Task postRequest (Runnable run) {
return DEFAULT.post (run);
}
/** This methods asks the request processor to start given
* runnable after timeToWait milliseconds. The default priority is Thread.MIN_PRIORITY.
*
* @param run class to run
* @param timeToWait to wait before execution
* @return the task to control the request
*/
public static Task postRequest (final Runnable run, int timeToWait) {
return DEFAULT.post (run, timeToWait);
}
/** This methods asks the request processor to start given
* runnable after timeToWait milliseconds. Given priority is assigned to the
* request.
*
*
* @param run class to run
* @param timeToWait to wait before execution
* @param priority the priority from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY
* @return the task to control the request
*/
public static Task postRequest (final Runnable run, int timeToWait, int priority) {
return DEFAULT.post (run, timeToWait, priority);
}
/** Creates request that can be later started by setting its delay.
* The request is not immediatelly put into the queue. It is planned after
* setting its delay by setDelay method.
*
* @param run action to run in the process
* @return the task to control execution of given action
*/
public static Task createRequest (Runnable run) {
return DEFAULT.create (run);
}
//
// Implementation of the queue
//
/** Checks the timer queue. First of all sees whether there is
* the processor thread running. If not, starts one. Then checks if
* the time of execution of the first task has not changed. If so,
* wakeups the thread so it can replan itself.
* <P>
* The methods is always called with synchronization on waiting.
*/
void checkTimerQueue () {
if (thread == null) {
// only start processor thread
thread = new ProcessorThread (name, this);
thread.start ();
} else {
if (waiting.size () == 0) {
// nothing to plan
return;
}
// wakeup the thread to change what should be changed
waiting.notify ();
}
}
/** Holder for a task */
private final class Holder extends Object {
/** Comment out when subclasing exception.
*/
public void printStackTrace () {
}
public Task task;
public int priority;
public long time;
public Holder (Task t) {
task = t;
priority = t.priority;
time = t.time;
}
public String toString () {
Task t = task;
return t == null ? "null" : t.toString (); // NOI18N
}
}
/** The task describing the request task send to the processor.
*/
public final class Task extends org.netbeans.modules.debugger.support.util.Task {
/** time to run */
long time;
/** priority */
int priority;
/** holder that is currently representing the task in the queue */
Holder holder;
private long id;
/** @param run runnable to start
* @param delay amount of millis to wait
* @param priority the priorty of the task
*/
Task (Runnable run, long delay, int priority) {
super (run);
time = System.currentTimeMillis () + delay;
this.priority = priority;
id = counter++;
}
/** Creates new holder canceling the previous one (if any).
* !Can be called only under waiting lock!
*/
Holder createHolder () {
if (holder != null) {
holder.task = null;
}
return holder = new Holder (this);
}
/** Getter for amount of millis till this task
* is started.
* @return amount of millis
*/
public int getDelay () {
long delay = time - System.currentTimeMillis ();
if (delay < 0L) return 0;
if (delay > (long)Integer.MAX_VALUE) return Integer.MAX_VALUE;
return (int)delay;
}
/** Changes the delay to different level,
* if the task has not been run yet, it is replaned to
* the new time. If it has been finished, it is replaned
* to be started again.
*
* @param delay time in millis to wait
*/
public void schedule (int delay) {
synchronized (waiting) {
time = System.currentTimeMillis () + delay;
waiting.add (createHolder ());
checkTimerQueue ();
}
}
/** Removes the task from the queue.
*
* @return true if the task has been removed from the queue,
* false it the task has already been processed
*/
public boolean cancel () {
synchronized (waiting) {
if (holder != null) {
holder.task = null;
holder = null;
return true;
} else {
return false;
}
}
}
/** Current priority of the task.
*/
public int getPriority () {
return priority;
}
/** Changes priority to new one. If the task has been
* already run, do not plan it again.
*
*/
public void setPriority (int priority) {
if (this.priority != priority) {
if (priority < Thread.MIN_PRIORITY) priority = Thread.MIN_PRIORITY;
if (priority > Thread.MAX_PRIORITY) priority = Thread.MAX_PRIORITY;
synchronized (waiting) {
this.priority = priority;
if (holder != null) {
waiting.add (createHolder ());
checkTimerQueue ();
}
}
}
}
/** This method is an implementation of the waitFinished method
* in the RequestProcessor.Task. It check the current thread if it is
* request processor thread.
*
* This implemetation run the task and then returns.
*/
void waitFinishedImpl () {
if (isRequestProcessorThread()) {
// one thread runnig
boolean toRun = false;
synchronized (waiting) {
if (holder != null) {
holder.task = null;
holder = null;
toRun = true;
}
}
if (toRun) {
run();
}
}
else {
/* JST: new threading model does not need this.
if (System.getProperty("netbeans.debug.threads") != null) {
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println ("WARNING: EventDispathThread is waiting for request processor task.");
Thread.dumpStack();
}
}
*/
super.waitFinishedImpl();
}
}
/** @return string representation */
public String toString () {
return super.toString () + " [" + (time - System.currentTimeMillis ()) + ", " + priority + ']'; // NOI18N
}
}
/** Processor thread.
*/
private final static class ProcessorThread extends Thread {
/** sorted set of all task that are waiting to be processed
* objects of type (Holder), sorted by priority
* @associates Holder
*/
private TreeSet pending = new TreeSet (new PriorityComp ());
/** reference to the processor */
private Reference requestProcessor;
/** stops the processor if true */
private boolean stop;
/** previous priority */
private int priority;
/** last sleep time */
Object sleep;
/** last delay */
long at;
/** Constructor. */
public ProcessorThread (String name, RequestProcessor requestProcessor) {
super (
getTopLevelThreadGroup(),
name == null ?
("Debugger Request Processor") // NOI18N
:
name
);
setDaemon (true);
priority = getPriority ();
this.requestProcessor = new WeakReference (requestProcessor);
}
/** Stops the processing (must have synch on the waiting) */
public void stopProcessing () {
stop = true;
interrupt ();
}
/** Copies objects from the waiting queue that are ready to be
* processed into pending queue. This method blocks when the
* the pending queue is empty.
*
* <P>
* Called under lock on waiting.
* @return the amount of time to wait till next event should be
* posted from waiting to pending (0 means infinity)
*/
private long waitingToPending () {
RequestProcessor rp = (RequestProcessor)requestProcessor.get ();
if (rp == null || rp.waiting.isEmpty ()) {
// wait for ever
return 0L;
}
Iterator it = rp.waiting.iterator ();
while (it.hasNext ()) {
Holder holder = (Holder)it.next ();
if (holder.task == null) {
// continue
it.remove ();
continue;
}
long diff = holder.time - System.currentTimeMillis ();
/* if (debug != null && pending.size () > 15) {
System.err.println ("waitingToPending: " + holder + " has diff: " + diff);
}
*/
if (diff > 0L) {
// this task should be run in future =>
// return the amount of time to wait
return diff;
}
it.remove ();
// put the holder into the pending queue
pending.add (holder);
}
// pending is not empty => do not wait
// pending is empty => wait forever
return 0L;
}
/** Synchronization object or exception.
*/
private Object synch () {
RequestProcessor rp = (RequestProcessor)requestProcessor.get ();
if (rp == null) {
throw new IllegalStateException ();
}
return rp.waiting;
}
/** Processor of requests.
*/
public void run () {
int priority = getPriority ();
while (!stop) {
Task t;
Holder h;
Iterator first;
synchronized (synch ()) {
for (;;) {
h = null;
t = null;
first = null;
// either fills the pending set or return time to
// wait for another request processing
long w = waitingToPending ();
if (!pending.isEmpty ()) {
break;
}
// wait the given time
try {
if (debug != null && requestProcessor.get () == DEFAULT) {
debug.notifySleep ((int)w);
}
synch ().wait (w);
} catch (InterruptedException ex) {
if (stop) {
return;
} else {
throw new InternalError ();
}
}
}
// take first holder (pending is not empty)
first = pending.iterator ();
h = (Holder)first.next ();
first.remove ();
// take the task
t = h.task;
if (t != null) {
// mark as being processed
t.holder = null;
} else {
continue;
}
}
// run the task
int p = t.getPriority ();
if (priority != p) {
setPriority (priority = p);
}
try {
// debug code
if (debug != null && requestProcessor.get () == DEFAULT) {
System.err.println ("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
debug.notifyRun (t, h);
h.printStackTrace();
}
// end of debug
t.run ();
if (debug != null && requestProcessor.get () == DEFAULT) {
System.err.println ("Task finished: " + t);
debug.printRequestProcessor ();
System.err.println ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
}
} catch (ThreadDeath td) {
// do not catch this
throw td;
} catch (Throwable ex) {
if (System.getProperty ("netbeans.debug.exceptions") != null) {
System.err.println("Request processor thread exception!");
ex.printStackTrace();
}
}
}
} // run
}
/**
* @return a top level ThreadGroup. The method ensures that even
* RequestProcessors created by internal execution will survive the
* end of the task.
*/
static ThreadGroup getTopLevelThreadGroup() {
java.security.PrivilegedAction run = new java.security.PrivilegedAction() {
public Object run() {
ThreadGroup current = Thread.currentThread().getThreadGroup();
while (current.getParent() != null) {
current = current.getParent();
}
return current;
}
};
return (ThreadGroup) java.security.AccessController.doPrivileged(run);
}
/** Comparator that compares times */
private static final class TimeComp extends Object implements Comparator {
/** Compares to different RequestProcessor.Task object.
* First of all compares the time and then it looks for the priority.
*
*/
public int compare (Object o1, Object o2) {
Holder h1 = (Holder)o1;
Holder h2 = (Holder)o2;
if (h1.time == h2.time) {
Task t1 = h1.task;
Task t2 = h2.task;
// choose anything
if (h1.task == h2.task)
return 0;
long code = System.identityHashCode (t1) - System.identityHashCode (t2);
if (code == 0)
code = h1.task.id - h2.task.id;
return (code > 0) ? 1 : -1;
} else {
// compare on time
if (h1.time > h2.time) {
return 1;
} else {
return -1;
}
}
}
}
/** Comparator that compares priorities */
private static final class PriorityComp extends Object implements Comparator {
/** Compares to different RequestProcessor.Task object.
* First of all compares the time and then it looks for the priority.
*
*/
public int compare (Object o1, Object o2) {
Holder h1 = (Holder)o1;
Holder h2 = (Holder)o2;
if (h1.priority == h2.priority) {
Task t1 = h1.task;
Task t2 = h2.task;
// choose anything
if (h1.task == h2.task)
return 0;
long code = System.identityHashCode (t1) - System.identityHashCode (t2);
if (code == 0)
code = h1.task.id - h2.task.id;
return (code > 0) ? 1 : -1;
} else {
// compare on priority
if (h1.priority < h2.priority) {
return 1;
} else {
return -1;
}
}
}
}
//
// Hacking code to allow debugging of request processors
//
private static Debug debug;
static {
if (System.getProperty ("netbeans.debug.requests") != null) {
debug = new Debug ();
} else {
TopManager tm = TopManager.getDefault ();
if (tm != null) {
tm.getWindowManager ().getMainWindow ().addKeyListener (
new Debug ()
);
}
}
}
private static class Debug extends Object implements java.awt.event.KeyListener {
public void keyReleased(final java.awt.event.KeyEvent p0) {
}
public void keyPressed(final java.awt.event.KeyEvent ev) {
if (
(ev.getModifiers () & java.awt.event.KeyEvent.CTRL_MASK) != 0 &&
(ev.getModifiers () & java.awt.event.KeyEvent.ALT_MASK) != 0 &&
ev.getKeyCode () == java.awt.event.KeyEvent.VK_F10
) {
debug = this;
printRequestProcessor ();
}
}
public void keyTyped(final java.awt.event.KeyEvent ev) {
}
private void printRequestProcessor () {
ProcessorThread thread = DEFAULT.thread;
if (thread != null) {
System.err.println("Content of " + thread + " sleep for: " + thread.sleep + " ago: " + (System.currentTimeMillis () - thread.at));
} else {
System.err.println("Content of " + thread);
}
java.util.LinkedList ll = new java.util.LinkedList (DEFAULT.waiting);
print (ll);
if (thread != null) {
System.err.println("Pending requests");
print (new LinkedList (thread.pending));
}
}
private void print (Collection ll) {
Iterator it = ll.iterator ();
int i = 0;
while (it.hasNext ()) {
Holder h = (Holder)it.next ();
Task t = h.task;
System.err.print(" ");
System.err.print(++i);
System.err.print(". ");
System.err.println(t);
}
}
public void notifySleep (int timeOut) {
ProcessorThread thread = DEFAULT.thread;
System.err.println("Sleeping for " + timeOut + " " + thread);
}
public void notifyPost (Task t) {
ProcessorThread thread = DEFAULT.thread;
System.err.println("Post: " + t + " to " + thread);
// Thread.dumpStack();
printRequestProcessor ();
}
public void notifyRun (Task t, Holder h) {
ProcessorThread thread = DEFAULT.thread;
System.err.println("Run: " + t + " to " + thread + " because of holder: " + h);
}
}
//
// End of hacking code
//
/*
public static void main (String [] args) throws Exception {
class Run extends Object implements Runnable {
private String name;
public Run (String n) {
name = n;
}
public void run () {
try {
System.out.println("In: " + name);
Thread.sleep (3000);
System.out.println("Out: " + name);
} catch (Exception ex) {
}
}
public String toString () {
return name;
}
}
Task t1 = RequestProcessor.postRequest (new Run ("First"));
Task t2 = RequestProcessor.postRequest (new Run ("Min"), 500, Thread.MIN_PRIORITY);
Task t3 = RequestProcessor.postRequest (new Run ("Max"), 2500, Thread.MAX_PRIORITY);
t1.waitFinished ();
System.out.println("t1 finished");
t2.waitFinished ();
System.out.println("t2 finished");
t3.waitFinished ();
System.out.println("t3 finished");
System.out.println("Finished");
}
*/
}
/*
* $Log:
* 1 Gandalf-post-FCS1.0 3/28/00 Daniel Prusa
* $
*/